package com.powernode.config;

import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.powernode.constant.AuthConstants;
import com.powernode.constant.BusinessEnum;
import com.powernode.constant.HttpConstants;
import com.powernode.impl.UserDetailServiceImpl;
import com.powernode.model.LoginResult;
import com.powernode.model.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.authentication.AccountExpiredException;
import org.springframework.security.authentication.AccountStatusException;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.InternalAuthenticationServiceException;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;

import java.io.PrintWriter;
import java.time.Duration;
import java.util.UUID;

/**
 * @Description: SpringSecurity配置类
 * @Author: Tong Ziyu
 * @Date: 2024/10/22 15:56
 */
@Configuration
@Slf4j
public class AuthSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Autowired
    private UserDetailServiceImpl userDetailService;

    @Autowired
    private ObjectMapper objectMapper;

    /**
     * 设置Security框架认证流程
     *
     * @param auth
     * @throws Exception
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        // 走自定义的认证流程
        auth.userDetailsService(userDetailService);

    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // 关闭跨站请求伪造
        http.cors().disable();

        // 关闭跨域请求
        http.csrf().disable();

        // 关闭session使用策略
        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);

        // 配置登录信息
        // 配置登录的url
        http.formLogin()
                // 设置登录的uri
                .loginProcessingUrl(AuthConstants.LOGIN_URI)
                // 设置登录成功返回json
                .successHandler(authenticationSuccessHandler())
                // 设置登录失败返回json
                .failureHandler(authenticationFailureHandler());



        // 配置登出信息
        http.logout()
                // 设置登出的uri
                .logoutUrl(AuthConstants.LOGOUT_URI)
                // 配置登出成功返回json
                .logoutSuccessHandler(logoutSuccessHandler());


        // 要求所有请求都进行身份认证
        http
                .authorizeHttpRequests()
                .anyRequest()
                // 设置所有的请求都需要身份认证
                .authenticated();

        // 放开登录请求
        http.authorizeRequests().mvcMatchers(AuthConstants.LOGIN_URI).permitAll();

    }


    /**
     * 登录成功处理器
     * @return
     */
    @Bean
    public AuthenticationSuccessHandler authenticationSuccessHandler() {
        return (request, response, authentication) -> {
            // 登录成功
            // 设置响应头
            response.setContentType(HttpConstants.APPLICATION_JSON);
            response.setCharacterEncoding(HttpConstants.CHARACTER_ENCODING);

            // 登录成功之后,在这里生成Token,这个token值 只是存在redis中的key,对应的value是用户信息
            // UUID : 用户信息
            String token = UUID.randomUUID().toString();

            // 从security框架中获取用户信息
            UserDetails details = (UserDetails) authentication.getPrincipal();

            String jsonString = JSONObject.toJSONString(details);

            //log.info("#登录成功-->用户信息:{},用户token值:{}", jsonString, token);

            // 将token和用户信息存到redis中
            stringRedisTemplate
                    .opsForValue()
                    .set(AuthConstants.LOGIN_TOKEN_PREFIX + token, jsonString, Duration.ofSeconds(AuthConstants.TOKEN_TIME));

            // 创建登录统一结果对象
            LoginResult build = LoginResult
                    .builder()
                    .accessToken(token)
                    .expireIn(AuthConstants.TOKEN_TIME)
                    .build();

            // 创建响应结果对象,将token响应给前端
            Result<LoginResult> success = Result.success(build);

            // 返回结果
            PrintWriter writer = response.getWriter();
            String resultJson = objectMapper.writeValueAsString(success);
            writer.write(resultJson);
            writer.flush();
            writer.close();
        };
    }

    /**
     * 登录失败处理器
     * @return
     */
    @Bean
    public AuthenticationFailureHandler authenticationFailureHandler() {
        return (request, response, exception) -> {
            // 登录失败
            response.setContentType(HttpConstants.APPLICATION_JSON);
            response.setCharacterEncoding(HttpConstants.CHARACTER_ENCODING);

            // 创建统一返回结果对象
            Result result = new Result();

            result.setCode(BusinessEnum.OPERATION_FAIL.getCode());

            if (exception instanceof BadCredentialsException) {
                result.setMsg("用户名或密码错误");
            } else if (exception instanceof UsernameNotFoundException) {
                result.setMsg("用户名不存在");
            } else if (exception instanceof AccountExpiredException) {
                result.setMsg("账号状态异常,请联系管理员!1");
            } else if (exception instanceof AccountStatusException) {
                result.setMsg("账号状态异常,请联系管理员!2");
            } else if (exception instanceof InternalAuthenticationServiceException) {
                result.setMsg(exception.getMessage());
            } else {
                result.setMsg(BusinessEnum.OPERATION_FAIL.getDesc());
            }

            PrintWriter writer = response.getWriter();
            String resultJson = objectMapper.writeValueAsString(result);
            writer.write(resultJson);
            writer.flush();
            writer.close();
        };
    }

    /**
     * 登出成功处理器
     * @return
     */
    @Bean
    public LogoutSuccessHandler logoutSuccessHandler() {
        return (request, response, authentication) -> {
            // 登出成功
            response.setContentType(HttpConstants.APPLICATION_JSON);
            response.setCharacterEncoding(HttpConstants.CHARACTER_ENCODING);

            // 从redis中删除对应的token
            String authorization = request.getHeader(AuthConstants.AUTHORIZATION);

            String token = authorization.replaceFirst(AuthConstants.BEARER, "");

            stringRedisTemplate.delete(AuthConstants.LOGIN_TOKEN_PREFIX + token);

            // 删除完token后 返回成功消息
            Result result = Result.success("登出成功");

            PrintWriter writer = response.getWriter();
            String resultJson = objectMapper.writeValueAsString(result);
            writer.write(resultJson);
            writer.flush();
            writer.close();
        };
    }

    /**
     * 密码加密器
     *
     * @return
     */
    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder() {

        log.info("password encoder finished!");
        return new BCryptPasswordEncoder();
    }

}
